#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <errno.h>
#include <termios.h>
#include <fcntl.h>
#include <unistd.h>
#include <arpa/inet.h>

#include "ftpc.h"
#include "ftpc_log.h"
#include "ftpc_tcp.h"
#include "ftpc_pi.h"
#include "ftpc_util.h"
#include "ftpc_event.h"
#include "ftpc_dtp.h"
#include "ftpc_ctl.h"


#define FTPC_CMD_REQUEST_MAXLEN 1023

static int __ctl_ParseReply(FTPC_BUF_HEAD_S *pstHead, FTPC_PI_CONTEXT_S* pstPICtx)
{
    FTPC_BUF_S *pstBuf;
    int iErr = FTPC_PI_AGAIN;
    int iReplyCode = 0;

    pstBuf = pstHead->pstHead;

    while (pstBuf != NULL && pstBuf->ulDataSize > 0)
    {
        pstBuf->pcBuf[pstBuf->ulDataSize] = '\0';
        printf("%s", pstBuf->pcBuf);

        iErr = FTPC_pi_ParseReply(pstPICtx, pstBuf->pcBuf, pstBuf->ulDataSize);
        pstHead->pstHead = pstBuf->pstNext;
        free(pstBuf);
        pstBuf = pstBuf->pstNext;

        if (iErr != FTPC_PI_AGAIN)
        {
            break;
        }
    }

    FTPC_buf_Reset(pstHead);

    return iErr;
}

static void __ctl_CloseConn(void)
{
    FTPC_dtp_CloseSock();

    if (g_stFTPCClient.iCtlSock != -1)
    {
        FTPC_LOG_INFO("Closed control connection, fd=%d", g_stFTPCClient.iCtlSock);

        FTPC_event_DeleteFd(g_stFTPCClient.iCtlSock);
        close(g_stFTPCClient.iCtlSock);
        g_stFTPCClient.iCtlSock = -1;
    }
}

static int __ctl_DoConnected(void)
{
    int iErr;
    
    if (g_stFTPCClient.stPICtx.iReplyCode != 220)
    {
        return -1;
    }

    iErr = FTPC_util_GetUserInput("Name: ", &g_stFTPCClient.stCmdInfo);
    if (iErr == 0)
    {
        iErr = FTPC_ctl_SendRequest(FTPC_CMD_USER, "%s", g_stFTPCClient.stCmdInfo.apcItems[0]);
        g_stFTPCClient.stCmdInfo.uiCount = 0;
    }

    return iErr;
}

static int __ctl_DoLoginUser(void)
{
    char* pcPassword;
    int iErr = -1;
    struct termios new_setting, init_setting;
    FTPC_CMD_INFO_S* pstCmdInfo = &g_stFTPCClient.stCmdInfo;

    if (g_stFTPCClient.stPICtx.iReplyCode != 331)
    {
        return iErr;
    }

    FTPC_event_DeleteFd(g_stFTPCClient.iCtlSock);

    pcPassword = NULL;
    if (pstCmdInfo->uiCount < 3)
    {
        // 在 程序输入密码时候 关闭 输入端的回显
        tcgetattr(STDIN_FILENO, &init_setting);
        new_setting = init_setting;
        new_setting.c_lflag &= ~ECHO;
        tcsetattr(STDIN_FILENO, TCSANOW, &new_setting);

        iErr = FTPC_util_GetUserInput("Password: ", pstCmdInfo);
        if (iErr == 0)
        {
            pcPassword = pstCmdInfo->apcItems[0];
        }

        // 恢复设置
        tcsetattr(STDIN_FILENO, TCSANOW, &init_setting);
        printf("\n");
    }
    else
    {
        pcPassword = pstCmdInfo->apcItems[2];
    }

    if (pcPassword != NULL)
    {
        iErr = FTPC_ctl_SendRequest(FTPC_CMD_PASS, "%s", pcPassword);
    }

    return iErr;
}

static int __ctl_DoLoginPasswd(void)
{
    if (g_stFTPCClient.stPICtx.iReplyCode != 230)
    {
        g_stFTPCClient.enState = FTPC_STATE_LOGIN_FAILED;
        return -1;
    }

    FTPC_event_DeleteFd(g_stFTPCClient.iCtlSock);

    return 0;
}

static int __ctl_OpenDataConnection()
{
    FTPC_PI_CONTEXT_S* pstPiCtx = &g_stFTPCClient.stPICtx;
    char* pcTemp = pstPiCtx->szAddrPort;
    unsigned short usPort = 0;
    unsigned char ucUnit = 0;
    unsigned char ucCommaCount = 0;
    int iSockFd;

    if (pstPiCtx->uiAddrPortLen == 0)
    {
        return -1;
    }

    for (ucCommaCount = 0; *pcTemp != '\0' && ucCommaCount < 4; pcTemp++)
    {
        if (*pcTemp == ',')
        {
            ucCommaCount++;
            if (ucCommaCount == 4)
            {
                *pcTemp = '\0';
            }
            else
            {
                *pcTemp = '.';
            }
        }
    }

    for (; *pcTemp != '\0'; pcTemp++)
    {
        if (*pcTemp == ',')
        {
            usPort |= ucUnit;
            usPort <<= 8;
            ucUnit = 0;
        }
        else
        {
            ucUnit = ucUnit * 10 + (*pcTemp - '0');
        }
    }

    usPort |= ucUnit;

    return FTPC_dtp_Connect(pstPiCtx->szAddrPort, usPort);;
}
// 
static int __ctl_DoNegotiatePort()
{
    int iErr = -1;
    unsigned int uiAddr;
    unsigned short usPort;
    FTPC_CMD_INFO_S* pstCmdInfo;

    if (g_stFTPCClient.stPICtx.iReplyCode != 200 &&
        g_stFTPCClient.stPICtx.iReplyCode != 227)
    {
        return iErr;
    }

    FTPC_event_DeleteFd(g_stFTPCClient.iCtlSock);

    if (g_stFTPCClient.pcTransferCmd == NULL)
    {
        return iErr;
    }

    if (BIT_TEST(g_stFTPCClient.uiFlags, FTPC_FLAG_PASSIVE))
    {
        iErr = __ctl_OpenDataConnection();
        if (iErr != 0)
        {
            return iErr;
        }
    }

    pstCmdInfo = &g_stFTPCClient.stCmdInfo;
    if (pstCmdInfo->uiCount > 1)
    {
        if (pstCmdInfo->uiCount > 2 && strcmp(g_stFTPCClient.pcTransferCmd, FTPC_CMD_STOR) == 0)
        {
            iErr = FTPC_ctl_SendRequest(g_stFTPCClient.pcTransferCmd, pstCmdInfo->apcItems[2]);
        }
        else
        {
            iErr = FTPC_ctl_SendRequest(g_stFTPCClient.pcTransferCmd, pstCmdInfo->apcItems[1]);
        }
    }
    else
    {
        iErr = FTPC_ctl_SendRequest(g_stFTPCClient.pcTransferCmd, NULL);
    }

    return iErr;
}

static int __ctl_DoToDataTransfer()
{
    int iFlags;
    int iFd = -1;
    int iRet = -1;
    FTPC_TRANSFER_TYPE_E enTransferType;
    FTPC_CMD_INFO_S* pstCmdInfo;
    char* pcFileName;
    mode_t iMode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
    
    if (g_stFTPCClient.stPICtx.iReplyCode != 150)
    {
        return iRet;
    }

    iRet = 0;
    pcFileName = NULL;
    pstCmdInfo = &g_stFTPCClient.stCmdInfo;
    enTransferType = FTPC_dtp_GetTransferType();
    switch(enTransferType)
    {
        case FTPC_TRANSFER_LIST:
        {
            if (pstCmdInfo->uiCount == 3)
            {
                iFlags = O_WRONLY | O_CREAT | O_TRUNC;
                pcFileName = pstCmdInfo->apcItems[2];
            }
            else
            {
                iFd = STDOUT_FILENO;
            }

            break;
        }
        case FTPC_TRANSFER_GET:
        {
            iFlags = O_WRONLY | O_CREAT | O_TRUNC;
            pcFileName = pstCmdInfo->apcItems[1];
            if (pstCmdInfo->uiCount == 3)
            {
                pcFileName = pstCmdInfo->apcItems[2];
            }

            break;
        }
        case FTPC_TRANSFER_PUT:
        {
            iFlags = O_RDONLY;
            pcFileName = pstCmdInfo->apcItems[1];

            break;
        }
        case FTPC_TRANSFER_MAX:
        default:
        {
            iRet = -1;
            break;
        }
    }
    
    if (iRet == 0)
    {
        if (pcFileName != NULL)
        {
            iFd = open(pcFileName, iFlags, iMode);
            if (iFd == -1)
            {
                FTPC_LOG_ERROR("open file %s error, %s", pcFileName, strerror(errno));
            }
        }

        if (iFd != -1)
        {
            FTPC_dtp_StartTransfer(iFd);
        }
        else
        {
            FTPC_dtp_CloseSock();
            iRet = -1;
        }
    }

    return iRet;
}

static int __ctl_DoInDataTransfer()
{
    int iErr = 0;

    if (g_stFTPCClient.stPICtx.iReplyCode == 226 ||
        g_stFTPCClient.stPICtx.iReplyCode == 250)
    {
        FTPC_dtp_StopTransfer();
        FTPC_event_DeleteFd(g_stFTPCClient.iCtlSock);
    }

    return iErr;
}

static void __ctl_DoQuitReply()
{
    exit(0);
}

static int __ctl_DoDisconnect()
{
    __ctl_CloseConn();

    return 0;
}

int FTPC_ctl_Connect(const char *pcHostName, unsigned short usPort)
{
    int iSockFd = FTPC_tcp_Connect(pcHostName, usPort);
    if (iSockFd == -1)
    {
        printf("%s: connect: %s\n", "ftpc", strerror(errno));
        return -1;
    }

    printf("Connected to %s.\n", pcHostName);

    g_stFTPCClient.usPort = usPort;
    strncpy(g_stFTPCClient.szHostName, pcHostName, FTPC_CLIENT_HOSTNAME_MAX + 1);
    
    g_stFTPCClient.iCtlSock = iSockFd;
    g_stFTPCClient.enState = FTPC_STATE_CONNECTED;
    g_stFTPCClient.enRecoverState = FTPC_STATE_CONNECTED;
    FTPC_event_AddFd(iSockFd, FTPC_EVENT_IN);

    return 0;
}

int FTPC_ctl_SendRequest(const char* pcCmd, const char* pcFmt, ...)
{
    int iLen;
    va_list ap;
    char szRequest[FTPC_CMD_REQUEST_MAXLEN + 1];
    size_t ulBufSize = FTPC_CMD_REQUEST_MAXLEN + 1;
    ssize_t lRet;

    if (pcFmt != NULL)
    {
        // command<SP>
        iLen = snprintf(szRequest, ulBufSize, "%s%s", pcCmd, FTPC_CMD_SEPERATOR);

        // command arguments
        va_start(ap, pcFmt);
        iLen += vsnprintf(szRequest + iLen, ulBufSize - iLen, pcFmt, ap);
        va_end(ap);

        // \r\n
        iLen += snprintf(szRequest + iLen, ulBufSize - iLen, FTPC_CMD_EOL);
    }
    else
    {
        // command\r\n
        iLen = snprintf(szRequest, ulBufSize, "%s%s", pcCmd, FTPC_CMD_EOL);
    }
    
    lRet = FTPC_tcp_Send(g_stFTPCClient.iCtlSock, szRequest, iLen);
    if (lRet == -1)
    {
        FTPC_LOG_ERROR("Failed to send request %s, %s", pcCmd, strerror(errno));

        printf("421 Service not available, remote server has closed connection\n");
        __ctl_CloseConn();

        return -1;
    }

    FTPC_event_AddFd(g_stFTPCClient.iCtlSock, FTPC_EVENT_IN);

    return 0;
}

// 处理控制连接读事件
int FTPC_ctl_RecvResponse(void)
{
    int iResult;
    char cLF;
    FTPC_STATE_E enNextState;

    FTPC_LOG_DEBUG("Received data from control connection, fd=%d", g_stFTPCClient.iCtlSock);

    if (g_stFTPCClient.iCtlSock == -1)
    {
        return -1;
    }

    //1. 接收服务器应答
    iResult = FTPC_tcp_Recv(g_stFTPCClient.iCtlSock, &g_stFTPCClient.stCtlBuf);
    if (iResult != 0)
    {
        FTPC_buf_Fini(&g_stFTPCClient.stCtlBuf);
        
        printf("421 Service not available, remote server has closed connection\n");
        __ctl_CloseConn();

        g_stFTPCClient.enState = FTPC_STATE_INIT;

        return -1;
    }

    // 2. 解析服务器应答
    iResult = __ctl_ParseReply(&g_stFTPCClient.stCtlBuf, &g_stFTPCClient.stPICtx);
    if (iResult == FTPC_PI_AGAIN)
    {
        // 等待接收后续数据才能继续解析
        return 0;
    }
    else if (iResult == FTPC_PI_FAILED)
    {
        FTPC_pi_Init(&g_stFTPCClient.stPICtx);
        FTPC_buf_Fini(&g_stFTPCClient.stCtlBuf);

        return -1;
    }

    // 3. 根据当前状态处理应答
    iResult = 0;
    switch (g_stFTPCClient.enState)
    {
        case FTPC_STATE_CONNECTED:
        {
            iResult = __ctl_DoConnected();
            enNextState = FTPC_STATE_LOGIN_USER;
            break;
        }
        case FTPC_STATE_LOGIN_USER:
        {
            iResult = __ctl_DoLoginUser();
            enNextState = FTPC_STATE_LOGIN_PASSWD;
            break;
        }
        case FTPC_STATE_LOGIN_PASSWD:
        {
            iResult = __ctl_DoLoginPasswd();
            enNextState = FTPC_STATE_LOGIN_SUCCESS;
            break;
        }
        case FTPC_STATE_NEGOTIATE_PORT:
        {
            iResult = __ctl_DoNegotiatePort();
            enNextState = FTPC_STATE_TO_DATA_TRANSFER;
            break;
        }
        case FTPC_STATE_TO_DATA_TRANSFER:
        {
            iResult = __ctl_DoToDataTransfer();
            enNextState = FTPC_STATE_IN_DATA_TRANSFER;
            break;
        }
        case FTPC_STATE_IN_DATA_TRANSFER:
        {
            iResult = __ctl_DoInDataTransfer();
            enNextState = FTPC_STATE_LOGIN_SUCCESS;
            break;
        }
        case FTPC_STATE_QUIT:
        {
            __ctl_DoQuitReply();
            break;
        }
        case FTPC_STATE_DISCONNECTED:
        {
            iResult = __ctl_DoDisconnect();
            enNextState = FTPC_STATE_INIT;
            break;
        }
        default:
        {
            enNextState = g_stFTPCClient.enState;
            break;
        }
    }

    FTPC_pi_Init(&g_stFTPCClient.stPICtx);

    if (iResult == 0)
    {
        g_stFTPCClient.enState = enNextState;
        if (g_stFTPCClient.enState == FTPC_STATE_LOGIN_SUCCESS || 
            g_stFTPCClient.enState == FTPC_STATE_CONNECTED ||
            g_stFTPCClient.enState == FTPC_STATE_DISCONNECTED )
        {
            g_stFTPCClient.enRecoverState = g_stFTPCClient.enState;
        }
    }
    else
    {
        FTPC_event_DeleteFd(g_stFTPCClient.iCtlSock);
        g_stFTPCClient.enState = g_stFTPCClient.enRecoverState;
    }

    return iResult;
}


void FTPC_ctl_Init(void)
{
    g_stFTPCClient.iCtlSock = -1;
    FTPC_buf_Init(&g_stFTPCClient.stCtlBuf, FTPC_CLIENT_BUF_MAXLEN);
    FTPC_pi_Init(&g_stFTPCClient.stPICtx);
}